package neiltwist.cfd.lightweightakka;


public class CFDSolver {

	//	private static final Random randomGenerator = new Random();

	// Default values. Lets use these for now...
	private int N=256; // grid resoultion
	private float timeStep=0.1f;
	private float diffusionRate=0.1f;
	private float viscosity=0.1f;

	private float force=1.0f;
	private float source=100.0f;

	private FloatArrayWrapper u, v, u_prev, v_prev;
	private FloatArrayWrapper dens, dens_prev;
	private FloatArrayWrapper densClick, uClick, vClick;
	
	private AkkaFactory akkaFactory;


	public CFDSolver(){
		u = new FloatArrayWrapper(N+2);
		v = new FloatArrayWrapper(N+2);
		u_prev = new FloatArrayWrapper(N+2);
		v_prev = new FloatArrayWrapper(N+2);

		dens = new FloatArrayWrapper(N+2);
		dens_prev = new FloatArrayWrapper(N+2);

		densClick = new FloatArrayWrapper(N+2);
		uClick = new FloatArrayWrapper(N+2);
		vClick = new FloatArrayWrapper(N+2);

		resetPrevGrids();

		for(int i=0; i<N+2; i++) {
			for(int j=0; j<N+2; j++) {
				if(i==j && i > N/3 && i<2*N/3) {
					dens_prev.set(i, j, source);
					u_prev.set(i, j, force);
					v_prev.set(i, j, force);
					//} else if (N-i==j){
					//	dens_prev.set(i, j, 100.0f);
				}
			}
		}
		
		akkaFactory = new AkkaFactory(8);

	}

	private void resetPrevGrids(){
		for(int i=0; i<N+2; i++) {
			for(int j=0; j<N+2; j++) {
				//dens_prev.set(i, j, 0.0f);
				//u_prev.set(i, j, 0.0f);
				//v_prev.set(i, j, 0.0f);
				densClick.set(i, j, 0.0f);
				uClick.set(i, j, 0.0f);
				vClick.set(i, j, 0.0f);
			}
		}
	}

	public int getGridSize() {
		return N;
	}


	public void addClick(ClickAction click, int windowSize) {
		System.out.println("Got click: "+click.getClickXStart()+" "+click.getClickYStart()+" "+click.getClickType());
		int x1 = ( (click.getClickXStart() * getGridSize() ) / windowSize);
		int y1 = ( (click.getClickYStart() * getGridSize() ) / windowSize);
		switch (click.getClickType()) {
		case LEFT:
			int x2 = ( (click.getClickXFinish() * getGridSize() ) / windowSize);
			int y2 = ( (click.getClickYFinish() * getGridSize() ) / windowSize);
			uClick.set(x1, y1, force);
			vClick.set(x1, y1, force);
			break;
		case RIGHT:
			densClick.set(x1, y1, source);
			break;
		default:
			break;
		}
	}

	private void addSource(int N, FloatArrayWrapper x, FloatArrayWrapper s, float dt){
		for ( int i = 0; i < (N+2); i++ ) {
			for ( int j = 0; j < (N+2); j++ ) {
				x.set(i,j, x.get(i,j) + dt*s.get(i,j));
			}
		}
	}

	private void copyClicks() {
		for ( int i = 0; i < (N+2); i++ ) {
			for ( int j = 0; j < (N+2); j++ ) {
				if(densClick.get(i, j)>0) {
					System.out.println("Setting density "+i+" "+j+" "+densClick.get(i, j));
					dens_prev.set(i, j, densClick.get(i, j));
				}
				if(uClick.get(i, j)>0) {
					System.out.println("Setting u "+i+" "+j+" "+uClick.get(i, j));
					u_prev.set(i, j, uClick.get(i, j));
				}
				if(vClick.get(i, j)>0) {
					System.out.println("Setting v "+i+" "+j+" "+vClick.get(i, j));
					v_prev.set(i, j, vClick.get(i, j));
				}
			}
		}
	}


	public void step(){
		copyClicks();
		stepVelocity(N,u,v,u_prev,v_prev,viscosity,timeStep);
		stepDensity(N,dens,dens_prev,u,v,diffusionRate,timeStep);
		resetPrevGrids();

	}

	private void swapArrays(FloatArrayWrapper a, FloatArrayWrapper b){
		float[][] tmpa=a.getTheArray();
		float[][] tmpb=b.getTheArray();
		a.setTheArray(tmpb);
		b.setTheArray(tmpa);
	}

	private void stepVelocity(int N, FloatArrayWrapper u, FloatArrayWrapper v, FloatArrayWrapper u0, FloatArrayWrapper v0,
			float visc, float dt) {
		addSource(N, u, u0, dt);
		addSource(N, v, v0, dt);
		
		swapArrays(u0, u);
		swapArrays(v0, v);
		
		diffuse(N, 1, u, u0, visc, dt);
		diffuse(N, 2, v, v0, visc, dt);
		
		project(N, u, v, u0, v0 );
		
		swapArrays(u0, u);
		swapArrays(v0, v);
		
		advect(N, 1, u, u0, u0, v0, dt );
		advect(N, 2, v, v0, u0, v0, dt );
		
		project(N, u, v, u0, v0 );
	}


	private void stepDensity(int N, FloatArrayWrapper density, FloatArrayWrapper density0,  FloatArrayWrapper u, FloatArrayWrapper v, float diffusionRate,
			float timeStep) {

		addSource(N, density, density0, timeStep);
		
		swapArrays(density,density0);
		
		diffuse(N, 0, density, density0, diffusionRate, timeStep);
		
		swapArrays(density,density0);
		
		advect(N, 0, density, density0, u, v, timeStep);
		
	}

	private void diffuse ( int N, int b, FloatArrayWrapper x, FloatArrayWrapper x0, float diff, float dt ) {
		float a = dt*diff*(float)(N * N);
		
		for ( int k=0 ; k<20 ; k++ ) {
			x.setTheArray(akkaFactory.calculateDiffuse(x, x0, a, N));
			setBoundary ( N, b, x );
		}
	}


	private void advect ( int N, int b, FloatArrayWrapper d, FloatArrayWrapper d0,
			FloatArrayWrapper u, FloatArrayWrapper v, float dt ) {
		int i0, j0, i1, j1;
		float x, y, s0, t0, s1, t1, dt0;
		dt0 = dt*(float)N;
		for ( int i=1 ; i<=N ; i++ ) {
			for ( int j=1 ; j<=N ; j++ ) {
				x = (float)i-dt0*u.get(i,j);
				y = (float)j-dt0*v.get(i,j);

				if (x<0.5) {
					x=0.5f;
				} else if (x > N+0.5f) {
					x=N + 0.5f;
				}
				i0=(int)x;
				i1=i0+1;

				if (y<0.5) {
					y=0.5f;
				} else if (y > N+0.5f) {
					y=N + 0.5f;
				}
				j0=(int)y;
				j1=j0 + 1;

				s1 = x-(float)i0;
				s0 = 1.0f-s1;
				t1 = y-(float)j0;
				t0 = 1.0f-t1;

				d.set(i,j,s0*(t0*d0.get(i0,j0)+t1*d0.get(i0,j1))+
						s1*(t0*d0.get(i1,j0)+t1*d0.get(i1,j1)));

			}
		}
		setBoundary ( N, b, d );
	}

	private void project ( int N, FloatArrayWrapper u, FloatArrayWrapper v, FloatArrayWrapper p, FloatArrayWrapper div ) {
		float h;
		h = 1.0f/N;
		for ( int i=1 ; i<=N ; i++ ) {
			for ( int j=1 ; j<=N ; j++ ) {
				div.set(i,j,-0.5f*h*(u.get(i+1,j)-u.get(i-1,j)+
						v.get(i,j+1)-v.get(i,j-1)));
				p.set(i,j,0);
			}
		}
		setBoundary ( N, 0, div );
		setBoundary ( N, 0, p );
		for ( int k=0 ; k<20 ; k++ ) {
			for ( int i=1 ; i<=N ; i++ ) {
				for ( int j=1 ; j<=N ; j++ ) {
					p.set(i,j, (div.get(i,j)+p.get(i-1,j)+p.get(i+1,j)+
							p.get(i,j-1)+p.get(i,j+1))/4);
				}
			}
			setBoundary ( N, 0, p );
		}
		for ( int i=1 ; i<=N ; i++ ) {
			for ( int j=1 ; j<=N ; j++ ) {
				u.set(i,j, u.get(i,j) - 0.5f*(p.get(i+1,j)-p.get(i-1,j))/h);
				v.set(i,j, v.get(i, j) - 0.5f*(p.get(i,j+1)-p.get(i,j-1))/h);
			}
		}
		setBoundary ( N, 1, u );
		setBoundary ( N, 2, v );
	}

	private void setBoundary ( int N, int b, FloatArrayWrapper x ) {
		for ( int i=1 ; i<=N ; i++ ) {
			x.set(0,i, b==1 ? -x.get(1,i) : x.get(1,i));
			x.set(N+1,i, b==1 ? -x.get(N,i) : x.get(N,i));
			x.set(i,0, b==2 ? -x.get(i,1) : x.get(i,1));
			x.set(i,N+1, b==2 ? -x.get(i,N) : x.get(i,N));
		}
		x.set(0,0, 0.5f*(x.get(1,0)+x.get(0,1)));
		x.set(0,N+1, 0.5f*(x.get(1,N+1)+x.get(0,N)));
		x.set(N+1,0, 0.5f*(x.get(N,0)+x.get(N+1,1)));
		x.set(N+1,N+1, 0.5f*(x.get(N,N+1)+x.get(N+1,N)));
	}



	public float getDensityValue(int x, int y) {
		//		System.out.println(x+","+y+"="+dens.get(x,y));
		return dens.get(x,y);
	}

	public float getVelocityUValue(int x, int y) {
		//System.out.println(x+","+y+"="+u.get(x,y));
		return u.get(x,y);
	}

	public float getVelocityVValue(int x, int y) {
		//System.out.println(x+","+y+"="+v.get(x,y));
		return v.get(x,y);
	}

}
